package pl.poznan.put.cs.to.tsp.localsearch;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import pl.poznan.put.cs.to.tsp.common.IResultHolder;
import pl.poznan.put.cs.to.tsp.common.Vertexes;
import pl.poznan.put.cs.to.tsp.listeners.TSPListener;

public class LocalSearch implements IResultHolder {
	private List<Integer> resA;
	private List<Integer> resB;

	private IResultHolder heuristic;
	private TSPListener tspListener;

	private boolean finished;

	private Random random;

	private TabuManager tabuManager;

	public LocalSearch(IResultHolder heuristic, TSPListener tspListener) {
		this.heuristic = heuristic;
		this.tspListener = tspListener;
		this.tabuManager = new TabuManager();
		this.initialize();
		this.performAlgorithm();
		this.tspListener.solutionGenerated(new LinkedList<Integer>(this.resA),
				new LinkedList<Integer>(this.resB), this.heuristic
						.getVertexes());
	}

	private void initialize() {
		this.resA = new ArrayList<Integer>(this.heuristic.getListA());
		this.resB = new ArrayList<Integer>(this.heuristic.getListB());
		this.random = new Random();
	}

	private void performAlgorithm() {
		this.finished = false;
		while (!this.finished) {
			EMoves[] moveTypes = EMoves.values();
			List<EMoves> eMoves = new ArrayList<EMoves>();
			for (int i = 0; i < moveTypes.length; i++) {
				eMoves.add(moveTypes[i]);
			}
			int numOfMoves = eMoves.size();

			for (int i = 0; i < numOfMoves; i++) {
				int index = this.random.nextInt(eMoves.size());
				EMoves move = eMoves.get(index);
				eMoves.remove(index);
				boolean improved = false;

				switch (move) {
				case EDGE_SWAP:
					//System.out.println("EDGE SWAP start");
					EdgeSwapMoveHandler edgeSwap = new EdgeSwapMoveHandler(
							this.resA, this.resB, this.heuristic.getVertexes(),
							this.tabuManager);
					improved = edgeSwap.hasImproved();
					this.resA = edgeSwap.getA();
					this.resB = edgeSwap.getB();
					//System.out.println("EDGE SWAP end");
					break;
				case VERTEX_EXCHANGE:
					// System.out.println("VERTEX EXCHANGE start");
					/*
					 * VertexExchangeHandler ve = new VertexExchangeHandler(
					 * this.resA, this.resB, this.heuristic.getVertexes(),
					 * this.tabuManager); improved = ve.hasImproved(); this.resA
					 * = ve.getA(); this.resB = ve.getB();
					 */
					// System.out.println("VERTEX EXCHANGE end");
					improved = false;
					break;
				case VERTEX_TRANSFER:
					//System.out.println("VERTEX TRANSFER start");
					VertexTransferHandler vt = new VertexTransferHandler(
							this.resA, this.resB, this.heuristic.getVertexes(),
							this.tabuManager);
					improved = vt.hasImproved();
					this.resA = vt.getA();
					this.resB = vt.getB();
					//System.out.println("VERTEX TRANSFER end");
					break;
				case VERTEX_TRAVEL:
					//System.out.println("VERTEX TRAVEL start");
					VertexTravelHandler vtr = new VertexTravelHandler(
							this.resA, this.resB, this.heuristic.getVertexes(),
							this.tabuManager);
					improved = vtr.hasImproved();
					this.resA = vtr.getA();
					this.resB = vtr.getB();
					//System.out.println("VERTEX TRAVEL end");
					break;
				default:
					//System.out.println("WRONG MOVE!");
				}

				if (improved) {
					break;
				}
				if (!improved && eMoves.isEmpty()) {
					this.finished = true;
				}
			}

			this.tspListener.solutionGenerated(new LinkedList<Integer>(
					this.resA), new LinkedList<Integer>(this.resB),
					this.heuristic.getVertexes());
		}
	}

	private int calculateLength(List<Integer> res1) {
		LinkedList<Integer> res = new LinkedList<Integer>(res1);
		int distance = 0;
		int first = res.getFirst();
		int last = res.getLast();
		int before = -1;

		for (Integer i : res) {
			if (before == -1) {
				distance += this.heuristic.getVertexes().getDistanceBetween(
						first, last);
			} else {
				distance += this.heuristic.getVertexes().getDistanceBetween(
						before, i);
			}
			before = i;
		}

		return distance;
	}

	@Override
	public int getLengthA() {
		return calculateLength(this.resA);
	}

	@Override
	public int getLengthB() {
		return calculateLength(this.resB);
	}

	@Override
	public LinkedList<Integer> getListA() {
		return new LinkedList<Integer>(this.resA);
	}

	@Override
	public LinkedList<Integer> getListB() {
		return new LinkedList<Integer>(this.resB);
	}

	@Override
	public Vertexes getVertexes() {
		return heuristic.getVertexes();
	}

	@Override
	public int compareTo(IResultHolder o) {
		int local = this.getLengthA() + this.getLengthB();
		int other = o.getLengthA() + o.getLengthB();
		return local - other;
	}
}
